/* vim:ft=objc:
 * $Id: POP3.m 141 2005-05-22 18:02:26Z bmoore $
 *
 * Copyright (c) 2004, 2005  Branden J. Moore.
 *
 * This file is part of MacBiff, and is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * MacBiff is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with MacBiff; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307 USA.
 *
 */

#import <AppKit/NSNibLoading.h>
#import "POP3.h"


#if 0
#  define EBUG 1
#endif
#import <MacBiffPlugIn/debug.h>
#import <MacBiffPlugIn/xmalloc.h>
#import <MacBiffPlugIn/PreferencesController.h>
#import <MacBiffPlugIn/Keychain.h>
#import <MacBiffPlugIn/Folder.h>
#import <MacBiffPlugIn/Message.h>

#include <errno.h>
#include <stdlib.h>
#include <ctype.h>

#define dwrite(a,b) \
	if ( 1 || print_debug & NETWORK_DEBUG ) do { \
		fprintf(_dbgfp, "%s%s", (a), (b)); \
	} while(0)


NSString* stripPrefix( NSString *str )
{
	NSRange range = [str rangeOfString: @": "];
	if ( range.location == NSNotFound )
		return str;

	range.location = range.location+2;
	range.length = [str length] - range.location;

	return [str substringWithRange: range];
}


@implementation POP3

+ (NSString *) plugInName
{
	return @"POP3";
}


+ (NSString *) plugInVersion
{
	return @"1.0.0";
}


+ (NSString *) plugInAuthor
{
	return @"Branden J. Moore <bmoore@forkit.org>";
}


+ (NSString *) plugInUrl
{
	return @"http://www.forkit.org/macbiff/";
}


- (void) statusAt: (double) pos withText: (NSString*) txt
{
	if ( actControl ) {
		[actControl updateStatus: pos withText: txt];
	}
}


- (void) statusBy: (double) pos withText: (NSString*) txt
{
	if ( actControl ) {
		[actControl incrementStatus: pos withText: txt];
	}
}


/* login:  Returns 0 if successfully logged in.  -1 otherwise, & sets errno */

- (int) connect
{
	int res;
	NSRange range;

	dprintf("Connecting with SSL: %s\n", [[mPrefs objectForKey:@"Use SSL"] boolValue] ? "YES" : "NO");
	res = [io connectWithSSL: [[mPrefs objectForKey:@"Use SSL"] boolValue]];
	if ( res != 0 ) {
		alert("Failed to Connect\n");
		return res;
	}

	/* Verify Appropriate Server and pre-authenticated state */
	NSString *str = [io readLine];

	range = [str rangeOfString: @"+OK"
			options: NSCaseInsensitiveSearch];
	if ( range.location == NSNotFound ) {
		[io disconnect];
		errno = EPFNOSUPPORT;
		return -1;
	}

	/* Log in */
	dwrite("> ", "USER Username\r\n");
	[io writeLine: [NSString stringWithFormat: @"USER %@\r\n",
				[mPrefs objectForKey: @"Username"]]];
	str = [io readLine];
	if ( !str ) {
		errno = ENOTCONN;
		[io disconnect];
		return -1;
	}
	dwrite("< ", [str cString]);
	if ( ![str hasPrefix: @"+OK"] ) {
		[io disconnect];
		NSAlert *alert = [NSAlert alertWithMessageText:
				@"Invalid Username!"
			defaultButton: Nil
			alternateButton: Nil
			otherButton: Nil
			informativeTextWithFormat:
			@"Login to %@ Failed!\n%@",
			acctName, str];
		[alert runModal];
		errno = ENOTCONN;
		return -1;
	}

	dwrite("> ", "PASS Password\r\n");
	[io writeLine: [NSString stringWithFormat: @"PASS %@\r\n",
				passwd]];
	str = [io readLine];
	if ( !str ) {
		errno = ENOTCONN;
		[io disconnect];
		return -1;
	}
	dwrite("< ", [str cString]);

	if ( ![str hasPrefix: @"+OK"] ) {
		NSAlert *alert = [NSAlert alertWithMessageText:
				@"Login Failed!"
			defaultButton: Nil
			alternateButton: Nil
			otherButton: Nil
			informativeTextWithFormat:
			@"Login to %@ Failed!\n%@",
			acctName,
			str];
		[alert runModal];
		errno = EAUTH;
		[io disconnect];
		return -1;
	}

	return 0;
}


/* Read in the preferences, or set up the defaults */
- (void) initializePrefs
{
	if ( ![mPrefs objectForKey: @"Hostname"] ) {
		/* No preferences set.  Set defaults */
		[mPrefs setObject: [NSNumber numberWithBool: YES] forKey: @"Fetch Headers"];
		[mPrefs setObject: @"" forKey: @"Hostname"];
		[mPrefs setObject: @"" forKey: @"Username"];
		[mPrefs setObject: [NSNumber numberWithInt: 995] forKey: @"Port"];
		[mPrefs setObject: [NSNumber numberWithBool: YES] forKey: @"Use Default Actions"];
		[mPrefs setObject: [NSNumber numberWithBool: YES] forKey: @"Use SSL"];
	}

	NSString *tpw = [keychain getPasswordForUser:
			[mPrefs objectForKey: @"Username"]
		onHost: [mPrefs objectForKey: @"Hostname"]
		atPort: [[mPrefs objectForKey: @"Port"] intValue]
		withProto: ([[mPrefs objectForKey: @"Use SSL"] boolValue]) ?
			kSecProtocolTypePOP3S : kSecProtocolTypePOP3
		byAsking: NO];

	if ( tpw ) {
		passwd = [[NSString alloc] initWithString: tpw];
	} /* Else, ask on first check */

}


- (id) initWithName: (NSString*) n
{
	if ( ( self = [super initWithName: n] ) != Nil ) {
		dprintf("Initializing POP3...\n");
		if ( ![NSBundle loadNibNamed: @"POP3" owner: self] ) {
			alert("Unable to Load POP3 Nib!\n");
		}
		mPrefs = [prefs account: n];
		folder = [[Folder alloc] initWithName: @"" andFullPath: @""];
		[folders addObject: folder];
		[self initializePrefs];
	}
	return self;
}


- (void) dealloc
{
	if ( folder ) [folder release];
	folder = Nil;
	[super dealloc];
}


- (void) setupView
{
	[fetchHeadersButton setState:
		[[mPrefs objectForKey: @"Fetch Headers"] boolValue] ?
			NSOnState : NSOffState];
	[loginNameTextField setStringValue:
		[mPrefs objectForKey: @"Username"]];
	[passwordTextField setStringValue: passwd ? passwd : @""];
	[portTextField setIntValue:
		[[mPrefs objectForKey: @"Port"] intValue]];
	[serverNameTextField setStringValue:
		[mPrefs objectForKey: @"Hostname"]];
	[useSSLButton setState:
		[[mPrefs objectForKey: @"Use SSL"] boolValue] ?
			NSOnState : NSOffState];
}


- (Message*) getMsg: (int) num
{
	NSDate *date = Nil;
	NSString *from = Nil;
	NSString *email = Nil;
	NSString *subject = Nil;
	unsigned uid = 0;
	Message *msg = Nil;
	NSString *str = Nil;

	str = [NSString stringWithFormat: @"UIDL %d\r\n", num];
	dwrite("> ", [str cString]);
	[io writeLine: str];
	str = [io readLine];
	dwrite("< ", [str cString]);
	if ( ![str hasPrefix: @"+OK"] ) return Nil;

	uid = [str hash];

	str = [NSString stringWithFormat: @"TOP %d 0\r\n", num];
	dwrite("> ", [str cString]);
	[io writeLine: str];

	str = [io readLine];
	dwrite("< ", [str cString]);
	while ( ![str hasPrefix: @"-ERR"] && ![str hasPrefix: @".\r\n"] ) {
		if ( [str hasPrefix: @"From: "] ) {
			str = stripPrefix( str );
			NSRange range = [str rangeOfString: @"<" options: NSBackwardsSearch];
			if ( range.location == NSNotFound ) {
				from = str;
				email = str;
			} else {
				from = [str substringWithRange: NSMakeRange(0, range.location-1)];
				email = [str substringWithRange: NSMakeRange(range.location+1, [str length] - range.location -2)];
			}
		} else if ( [str hasPrefix: @"Date: "] ) {
			str = stripPrefix( str );
			date = [NSCalendarDate dateWithNaturalLanguageString: str];
		} else if ( [str hasPrefix: @"Subject: "] ) {
			subject = stripPrefix( str );
		}

		str = [io readLine];
		dwrite("< ", [str cString]);
	}
	if ( !from ) from = @"";
	if ( !email ) email = @"";
	if ( !subject ) subject = @"";
	if ( !date ) date = [NSDate date];

	msg = [Message newMessageFrom: from
				email: email
			  withSubject: subject
			       atDate: date
			      withUid: uid];
	return msg;
}


- (int) checkMail
{
	int retval = 0;
	int res;
	NSAutoreleasePool *mPool = [[NSAutoreleasePool alloc] init];
	BOOL normal_end = YES;
	NSString *str = Nil;
	int i;

	stop_now = 0;
	newNew = newTotal = 0;

	dprintf("Account '%s' to check mail.\n", [acctName cString]);
	/* Verify if we have a password */
	if ( !passwd ) {
		NSString *tpw = [keychain getPasswordForUser:
				[mPrefs objectForKey: @"Username"]
			onHost: [mPrefs objectForKey: @"Hostname"]
			atPort: [[mPrefs objectForKey: @"Port"] intValue]
			withProto: ([[mPrefs objectForKey: @"Use SSL"] boolValue]) ?
				kSecProtocolTypePOP3S : kSecProtocolTypePOP3
			byAsking: YES];
		passwd = [tpw retain];
	}

	if ( !io ) {
		io = [[LineIO alloc] initWithHost:
				[mPrefs objectForKey: @"Hostname"]
			andPort: [[mPrefs objectForKey: @"Port"] intValue]];
	}
	@try {
	/* Connect */
		[self statusAt: 0.0 withText: @"Connecting..."];
		res = [self connect];

	/* Check Mail */
		dwrite("> ", "STAT\r\n");
		[io writeLine: [NSString stringWithString: @"STAT\r\n"]];
		str = [io readLine];
		dwrite("< ", [str cString]);
		if ( [str hasPrefix: @"+OK"] ) {
			int idx;
			Message *uidMsg = [[[Message alloc] init] autorelease];
			NSArray *arr = [str componentsSeparatedByString: @" "];
			newNew = newTotal = [[arr objectAtIndex: 1] intValue];

			NSArray *oldMsgList = [[folder messageList] copy];
			[folder resetMessageList];
			for ( i = 1 ; i <= (int)newTotal ; ++i ) {
				str = [NSString stringWithFormat: @"UIDL %d\r\n",
				    i];
				dwrite("> ", [str cString]);
				[io writeLine: str];
				str = [io readLine];
				dwrite("< ", [str cString]);
				if ( [str hasPrefix: @"+OK"] ) {
					[uidMsg setUID: [str hash]];
					if ( (idx = [oldMsgList indexOfObject: uidMsg]) != NSNotFound ) {
						Message *msg = [oldMsgList objectAtIndex: idx];
						[msg setNew: NO];
						[folder addMessage: msg];
					} else if ( [[mPrefs objectForKey: @"Fetch Headers"] boolValue] ) {
						Message *msg = [self getMsg: i];
						if ( msg )
							[folder addMessage: msg];
					} else {
						Message *msg = [uidMsg copy];
						[folder addMessage: msg];
						[msg release];
					}
				}
			}
		}

	/* Log Out */
		[self statusAt: 95.0 withText: @"Logging Out."];
		dprintf("Logging out...\n");
		dwrite("> ", "QUIT\r\n");
		[io writeLine: [NSString stringWithString: @"QUIT\r\n"]];
		str = [io readLine];
		dwrite("< ", [str cString]);
	}
	@catch ( NSException *excp ) {
		NSString *exn = [excp reason];
		alert("Exception was thrown\n");
		alert("Thrown: '%s'\n", [exn cString]);
		normal_end = NO;
	}
	@finally {
		dprintf("Disconnecting.\n");
		[self statusAt: 98.0 withText: @"Disconnecting."];
		[io disconnect];

	}

	if ( normal_end ) {
		total = newTotal;
		new = newNew;

		[self statusAt: 99.0 withText: @"Saving Information."];
	}

	if ( actControl )
		[actControl completed];

	[mPool release];
	stop_now = 0;
	return retval;
}


- (void) stopNow: (id) obj
{
	stop_now = 1;
}


- (unsigned int) total
{
	return total;
}


- (unsigned int) unread
{
	return total;
}


- (unsigned int) new
{
	return new;
}


- (IBAction)setFetchHeaders:(id)sender
{
	[mPrefs setObject: [NSNumber numberWithBool:
			[fetchHeadersButton state] == NSOnState]
		forKey: @"Fetch Headers"];
	[prefs commitAccounts];
}


- (IBAction)setLoginName:(id)sender
{
	[mPrefs setObject: [loginNameTextField stringValue]
		forKey: @"Username"];
	[prefs commitAccounts];
}


- (IBAction)setPassword:(id)sender
{
	if ( passwd ) [passwd release];
	passwd = [[NSString alloc] initWithString:
				[passwordTextField stringValue]];
	[keychain savePassword: passwd
		forUser: [mPrefs objectForKey: @"Username"]
		onHost: [mPrefs objectForKey: @"Hostname"]
		atPort: [[mPrefs objectForKey: @"Port"] intValue]
		withProto: ([[mPrefs objectForKey: @"Use SSL"] boolValue]) ?
			kSecProtocolTypePOP3S : kSecProtocolTypePOP3];
}


- (IBAction)setPort:(id)sender
{
	[mPrefs setObject: [NSNumber numberWithInt: [portTextField intValue]]
		forKey: @"Port"];
	[prefs commitAccounts];
}


- (IBAction)setServerName:(id)sender
{
	[mPrefs setObject: [serverNameTextField stringValue]
		forKey: @"Hostname"];
	[prefs commitAccounts];
}


- (IBAction)setUseSSL:(id)sender
{
	BOOL use_SSL = ([useSSLButton state] == NSOnState);
	[mPrefs setObject: [NSNumber numberWithBool: use_SSL]
		forKey: @"Use SSL"];
	[portTextField setIntValue: use_SSL ? 995 : 110];
	[mPrefs setObject: [NSNumber numberWithInt: [portTextField intValue]]
		forKey: @"Port"];
	[prefs commitAccounts];
}

@end
